package com.yhsimon.distributed_lock.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.yhsimon.distributed_lock.lock.DistributedLockClient;
import com.yhsimon.distributed_lock.lock.DistributedRedisLock;
import com.yhsimon.distributed_lock.mapper.LockMapper;
import com.yhsimon.distributed_lock.mapper.StockMapper;
import com.yhsimon.distributed_lock.pojo.Lock;
import com.yhsimon.distributed_lock.pojo.Stock;
import com.yhsimon.distributed_lock.service.StockService;
import com.yhsimon.distributed_lock.zk.ZkClient;
import com.yhsimon.distributed_lock.zk.ZkDistributedLock;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author： YHSimon
 * @date： 2022-09-03 21:23
 */
@Service
// @Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS )
public class StockServiceImpl implements StockService {
    // private Stock stock=new Stock();

    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private LockMapper lockMapper;

    // private ReentrantLock lock = new ReentrantLock();

    @Autowired
    private DistributedLockClient distributedLockClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ZkClient zkClient;

    @Value("${server.port}")
    private String port;


    /**
     * 基于MySQL
     */
    public void deduct() {
        try {
            // 加锁
            Lock lock = new Lock();
            lock.setLockName("lock");
            this.lockMapper.insert(lock);
            // 1.查询库存信息
            String stock = stringRedisTemplate.opsForValue().get("stock");

            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3. 扣减库存
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }
            // 解锁
            this.lockMapper.deleteById(lock.getId());

        } catch (Exception e) {
            e.printStackTrace();
            // 重试
            try {
                Thread.sleep(50);
                this.deduct();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * zookeeper实现分布式锁  也可参考基于zookeeper的Curator框架
     */
    public void deduct9() {
        ZkDistributedLock lock = zkClient.getLock("lock");
        lock.lock();

        try {
            // 1.查询库存信息
            String stock = stringRedisTemplate.opsForValue().get("stock");

            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3. 扣减库存
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }

            this.test(); // 测试可重入性
        } finally {
            lock.unlock();
        }
    }

    /**
     * 测试zk分布式的可重入
     */
    public void test() {
        ZkDistributedLock lock = this.zkClient.getLock("lock");
        try {
            lock.lock();
            System.out.println("测试可重入锁...");
        } finally {
            lock.unlock();
        }

    }

    /**
     * Redisson实现分布式锁
     */
    public void deduct8() {

        RLock lock = redissonClient.getLock("lock");
        lock.lock();

        try {
            // 1.查询库存信息
            String stock = stringRedisTemplate.opsForValue().get("stock");

            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    //3. 扣减库存
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void testReadLock() {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        rwLock.readLock().lock(10, TimeUnit.SECONDS);

        // rwLock.readLock().unlock();
    }

    @Override
    public void testWriteLock() {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        rwLock.writeLock().lock(10, TimeUnit.SECONDS);
        // rwLock.writeLock().unlock();
    }

    /**
     * 测试分布式信号量
     */
    @Override
    public void testSemaphore() {
        // 将semaphore作为key并缓存到redis中
        RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
        // 设置资源量 限流的线程数
        semaphore.trySetPermits(3);


        try {
            semaphore.acquire();
            // 将打印信息统一存储在redis中
            this.stringRedisTemplate.opsForList().rightPush("log", "10010获取了资源，开始处理业务逻辑。。。" + Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(10 + new Random().nextInt(10));
            this.stringRedisTemplate.opsForList().rightPush("log", "10010释放了资源，释放资源。。。" + Thread.currentThread().getName());
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    /**
     * 手写基于Redis的分布式锁
     */
    // 测试可重入 相当于A方法
    public void deduct7() {
        DistributedRedisLock redisLock = this.distributedLockClient.getRedisLock("lock");
        // 1. 查询库存信息
        try {
            redisLock.lock();
            String stock = stringRedisTemplate.opsForValue().get("stock");
            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                // 3. 扣减库存
                int st = Integer.parseInt(stock);
                if (st > 0) {
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }
            // 测试可重入性
            // this.test();
            // 模拟业务执行过长，判断redis的key是否能够自动续期
            // TimeUnit.SECONDS.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisLock.unlock();
        }
    }

    // 测试Redis分布式锁可重入 相当于B方法
    public void test2() {
        DistributedRedisLock lock = this.distributedLockClient.getRedisLock("lock");
        try {
            lock.lock();
            System.out.println("测试可重入锁...");
        } finally {
            lock.unlock();
        }

    }


    /**
     * Redis 分布式锁  218
     */
    public void deduct6() {
        String uuid = UUID.randomUUID().toString();
        // 加锁
        while (!stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS)) {
            // 重试：循环
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            // 1. 查询库存信息
            String stock = stringRedisTemplate.opsForValue().get("stock");
            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                // 3. 扣减库存
                int st = Integer.parseInt(stock);
                if (st > 0) {
                    stringRedisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }
        } finally {
            // 先判断是否自己的锁，再解锁  防误删
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then " +
                    "   return redis.call('del', KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";
            stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
           /* if (StringUtils.equals(stringRedisTemplate.opsForValue().get("lock"), uuid)) {
                stringRedisTemplate.delete("lock");
            }*/
        }
    }


    /**
     * Redis 乐观锁
     */
    public void deduct5() {

        stringRedisTemplate.execute(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                // watch
                operations.watch("stock");
                // 1. 查询库存信息
                String stock = operations.opsForValue().get("stock").toString();
                // 2. 判断库存是否充足;
                if (stock != null && stock.length() != 0) {
                    // 3. 扣减库存
                    int st = Integer.parseInt(stock);
                    if (st > 0) {
                        // multi
                        operations.multi();
                        operations.opsForValue().set("stock", String.valueOf(--st));
                        // exec
                        List exec = operations.exec();
                        // 如果执行事务的返回结果集为空，则代表减库存失败，重试
                        if (exec == null || exec.size() == 0) {
                            try {
                                // 防止栈溢出
                                Thread.sleep(40);
                                deduct();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        return exec;
                    }
                }
                return null;
            }
        });
    }


    /**
     * 乐观锁
     * 关闭@Transactional注解；
     */
    // @Transactional
    public void deduct4() {
        // 1. 查询库存信息（含版本号）
        List<Stock> stocks = this.stockMapper.selectList(new QueryWrapper<Stock>().eq("product_code", "1001"));
        // 取第一个库存
        Stock stock = stocks.get(0);
        // 2. 判断库存是否充足;
        if (stock.getCount() > 0) {
            //3. 扣减库存
            stock.setCount(stock.getCount() - 1);
            Integer version = stock.getVersion();
            stock.setVersion(version + 1);
            if (this.stockMapper.update(stock, new UpdateWrapper<Stock>().eq("id", stock.getId()).eq("version", version)) == 0) {
                //如果更新失败则重试
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.deduct();
            }
        }
    }

    /**
     * 悲观锁
     */
    @Transactional
    public void deduct3() {
        // 1. 查询库存信息并锁定库存信息
        List<Stock> stocks = this.stockMapper.queryStock("1001");
        // 取第一个库存
        Stock stock = stocks.get(0);
        // 2. 判断库存是否充足;
        if (stock.getCount() > 0) {
            //3. 扣减库存
            stock.setCount(stock.getCount() - 1);
            this.stockMapper.updateById(stock);
        }
    }


    /**
     * 一条SQL语句
     */
    // @Transactional
    public void deduct2() {
        // lock.lock();
        try {

            // update insert delete 写操作本身就会加锁
            // update db_stock set count=count-1 where product_code= '1001' and count>=1;

            this.stockMapper.updateStock("1001", 1);

            // Stock stock = this.stockMapper.selectOne(new QueryWrapper<Stock>().eq("product_code", "1001"));
            // if(stock!=null&&stock.getCount()>0){
            //     stock.setCount(stock.getCount()-1);
            //     this.stockMapper.updateById(stock);
            //     System.out.println("port"+port+"库存剩余:"+stock.getCount());
            // }
        } finally {
            // lock.unlock();
        }
    }


    /** synchronized
     * 3168 QPS
     */
    /*@Override
    public synchronized void deduct() {
        stock.setStock(stock.getStock()-1);
        System.out.println("库存余量："+stock.getStock());
    }*/


}
